package com.gcloud.mesh.analysis.timer;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

import org.jeecg.common.util.RedisUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import com.gcloud.mesh.analysis.dao.DemoDao;
import com.gcloud.mesh.analysis.dao.SimulateDao;
import com.gcloud.mesh.analysis.entity.DemoEntity;
import com.gcloud.mesh.analysis.entity.SimulateEntity;
import com.gcloud.mesh.asset.dao.IaasDao;
import com.gcloud.mesh.asset.enums.DeviceType;
import com.gcloud.mesh.header.vo.analysis.FitterResourceVo;
import com.gcloud.mesh.header.vo.asset.DeviceItemVo;
import com.gcloud.mesh.header.vo.asset.NodeItemVo;
import com.gcloud.mesh.threads.ThreadManager;
import com.gcloud.mesh.utils.MeshMathUtil;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Component
public class DemoDataTimer {
	
	@Autowired
	private DemoDao demoDao;
	
	@Autowired
	private IaasDao iaasDao;
	
	@Autowired
	private RedisUtil redisUtil;
	
	private static final String SIMULATE = "simulate";
	
	private static final String FITTER = "fitter";
	
	private static final String DATACENTER_CACHE_KEY = "mesh_asset_datacenters";
	
	private static final double[] fitterPower = {200, 210, 213 , 220, 222, 224, 230, 233, 240, 251, 255, 264, 271, 288};
	
	private SecureRandom rand = new SecureRandom();
	
	private Map<String, Double> dcSimulatePue = new ConcurrentHashMap<String, Double>(); 
	
	@Autowired
	private SimulateDao simulateDao;
	
	private Map<String, List<NodeItemVo>> nodeMaps = new HashMap<String, List<NodeItemVo>>();
	private Map<String, List<String>> fitterResourceMaps = new HashMap<String, List<String>>();
	private List<String> datacenterIds = null;
	
	//定时60秒同步一次资产
	@Scheduled(initialDelay = 30 * 1000, fixedDelay = 60 * 1000)
	private void executeCache() {
		updateLocalCache();		
	}
	
	@Scheduled(initialDelay = 30 * 1000, fixedDelay = 60 * 1000)
	private void executeFitter() {
		fitterAirWithNode();	
	}
	
	@Scheduled(initialDelay = 30 * 1000, fixedDelay = 60 * 1000)
	private void executeSimulate() {
		simulatePueWithNode();
	}
	
	@Scheduled(initialDelay = 10 * 1000, fixedDelay = 1 * 60 * 1000)
	private void executeDcWholePue() {
		datacenterIds = (List<String>)redisUtil.get(DATACENTER_CACHE_KEY);
		if(datacenterIds != null) {
			double dataX = adaptValue(randomChoose(fitterPower), -5d, 5d);
			datacenterIds.stream().forEach( s -> {
				double dataY = adaptValue(fxx(dataX), -0.2d, 0.2d);
				dcSimulatePue.put(s, dataY);
			});
		}
	}
	
	private void updateLocalCache() {
		datacenterIds = (List<String>)redisUtil.get(DATACENTER_CACHE_KEY);
		if(datacenterIds != null) {
			datacenterIds.parallelStream().forEach( d -> {
				List<NodeItemVo> nodes = iaasDao.listNode(d, null);
				List<FitterResourceVo> resources = iaasDao.listFitterResource(d);
				List<String> resourceIds = resources.stream().map( s -> s.getResourceId()).collect(Collectors.toList());
				nodeMaps.put(d,  nodes);
				fitterResourceMaps.put(d, resourceIds);
			});
		}
	}
	
	private void fitterAirWithNode() {
		if(datacenterIds != null) {
			datacenterIds.parallelStream().forEach( d -> {
				if(fitterResourceMaps.get(d) != null) {
					for(String resourceId: fitterResourceMaps.get(d)) {
						ThreadManager.submit(new Runnable() {

							@Override
							public void run() {
								// TODO Auto-generated method stub
								try {
									buildFitterData(resourceId);
								}catch(Exception e) {
									log.debug("[DemoDataTimer][fitterAirWithNode] demo data save failed :{}", e.getMessage());
								}
							}
							
						});
						
					}
				}
			});
		}
	}
	
	private void simulatePueWithNode() {
		if(datacenterIds != null) {
			datacenterIds.parallelStream().forEach( d -> {
				if(nodeMaps.get(d) != null) {
					List<String> nodeIds = nodeMaps.get(d).stream().map( s -> s.getId()).collect(Collectors.toList());
					for(String nodeId: nodeIds) {
						ThreadManager.submit(new Runnable() {
							
							@Override
							public void run() {
								// TODO Auto-generated method stub
								try {
//									buildSimulateData(nodeId);
									buildSimulateData(nodeId, d);
								}catch(Exception e) {
									log.debug("[DemoDataTimer][simulatePueWithNode] demo data save failed :{}", e.getMessage());
								}
							}	
						});
						
					}
				}
			});
		}
	}
	
	private void buildFitterData(String resourceId) {
		
		DemoEntity entity = new DemoEntity();
		double dataX = adaptValue(randomChoose(fitterPower), -5d, 5d);
		double dataY = adaptValue(fx(dataX), -1.5d, 1.5d);
		entity.setDataX(MeshMathUtil.setScale(2, dataX));
		entity.setDataY(MeshMathUtil.setScale(2, dataY));
		entity.setTimestamp(new Date());
		entity.setType(FITTER);
		entity.setResourceId(resourceId);
		demoDao.save(entity);
		
	}
	
	private void buildSimulateData(String deviceId) {
		
		DemoEntity entity = new DemoEntity();
		double dataX = adaptValue(randomChoose(fitterPower), -5d, 5d);
		double dataY = adaptValue(fxx(dataX), -0.2d, 0.2d);
		entity.setDataX(MeshMathUtil.setScale(2, dataX));
		entity.setDataY(MeshMathUtil.setScale(2, dataY));
		entity.setTimestamp(new Date());
		entity.setType(SIMULATE);
		entity.setResourceId(deviceId);
		demoDao.save(entity);
		
		List<SimulateEntity> simulates = simulateDao.findAll();
		for(SimulateEntity simulate: simulates) {
			if(simulate.getSimulateValue() == null || simulate.getSimulateValue() == 0) {
				simulate.setSimulateMetric(entity.getDataX());
				simulate.setSimulateValue(entity.getDataY());
				updateSimulate(simulate);
				continue;
			}
			if(simulate.getSimulateValue() != null && simulate.getSimulateValue() > entity.getDataY()) {
				simulate.setSimulateMetric(entity.getDataX());
				simulate.setSimulateValue(entity.getDataY());
				updateSimulate(simulate);
			}

		}
	}
	
	private void buildSimulateData(String deviceId, String dcId) {
		
		DemoEntity entity = new DemoEntity();
		double dataX = adaptValue(randomChoose(fitterPower), -5d, 5d);
		double dataY = dcSimulatePue.get(dcId);
		entity.setDataX(MeshMathUtil.setScale(2, dataX));
		entity.setDataY(MeshMathUtil.setScale(2, dataY));
		entity.setTimestamp(new Date());
		entity.setType(SIMULATE);
		entity.setResourceId(deviceId);
		demoDao.save(entity);
	}
	
	private double randomChoose(double[] data) {
		int len = data.length;
		int index = (int)(0 + rand.nextDouble()*(len - 1));
		return data[index];
	}
	
	/**
	 * y = k*x + b
	 * @param x
	 * @return
	 */
	private double fx(double x) {
		
		BigDecimal tempMin = new BigDecimal(200);
		BigDecimal tempMax = new BigDecimal(288);
		BigDecimal powerMin = new BigDecimal(25);
		BigDecimal powerMax = new BigDecimal(39.6);	
		BigDecimal scope = (powerMax.subtract(powerMin)).divide(tempMax.subtract(tempMin), 2, RoundingMode.HALF_UP);
		BigDecimal b = (powerMax.multiply(tempMin).subtract(powerMin.multiply(tempMax))).divide(tempMin.subtract(tempMax), 2, RoundingMode.HALF_UP);
		BigDecimal y = scope.multiply(new BigDecimal(x)).add(b);
		return y.doubleValue();
	}
	
	/**
	 * y = a*x*x + b*x + c
	 * y = 0.0006142587*x*x - 0.2953306386*x + 37.0028726056
	 * @param x
	 * @return
	 */
	private double fxx(double x) {
		BigDecimal a = new BigDecimal(0.0006142587);
		BigDecimal b = new BigDecimal(-0.2953306386);
		BigDecimal c = new BigDecimal(37.0028726056);
		BigDecimal y = a.multiply(new BigDecimal(x).pow(2)).add(b.multiply(new BigDecimal(x))).add(c);
		return y.doubleValue();
	}
	
	/**
	 * 随机调整值
	 * @param value
	 * @param lower < 0
	 * @param upper > 0
	 * @return
	 */
	private double adaptValue(double value, double lower, double upper) {
		double[] probs = {0.3, 0.3, 0.4};
		
		double r = rand.nextDouble();
        double sum = 0.0;
        int index = 0;
        for (int i = 0; i < probs.length; i++) {
            sum += probs[i];
            if (sum > r) {
            	index = i;
            	break;
            }    
        }
        double diff = 0;
        if(index == 1) {
        	diff = lower + rand.nextDouble() * (0 - lower);
        }
        if(index == 2) {
        	diff = 0 + rand.nextDouble() * (upper - 0);
        }

		return value + diff;
	}
	
	private void updateSimulate(SimulateEntity entity) {
		List<String> updates = new ArrayList<String>();
		updates.add("simulate_metric");
		updates.add("simulate_value");
		simulateDao.update(entity, updates);
	}

}
